package com.zqq.demo.camera1;

import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.*;
import android.os.Bundle;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.zqq.demo.R;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Camera3Activity extends AppCompatActivity
    implements SurfaceHolder.Callback, Camera.PreviewCallback, AudioRecord.OnRecordPositionUpdateListener {
    
    private static final int VIDEO_WIDTH = 1280;
    private static final int VIDEO_HEIGHT = 720;
    private static final int VIDEO_BITRATE = 4000000;
    private static final int VIDEO_FRAMERATE = 30;
    private static final int AUDIO_SAMPLE_RATE = 44100;
    private static final int AUDIO_BITRATE = 128000;
    
    private Camera mCamera;
    private SurfaceView mSurfaceView;
    private MediaCodec mVideoEncoder;
    private MediaCodec mAudioEncoder;
    private MediaMuxer mMuxer;
    private AudioRecord mAudioRecord;
    private int mVideoTrackIndex = -1;
    private int mAudioTrackIndex = -1;
    private boolean mMuxerStarted = false;
    private long mStartTime = 0;
    private boolean mIsRecording = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera3);
        
        mSurfaceView = findViewById(R.id.surface_view);
        mSurfaceView.getHolder().addCallback(this);

        Button record_button = findViewById(R.id.record_button);
        record_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(mIsRecording){
                    record_button.setText("开始录制");
                    stopRecording();
                }else{
                    record_button.setText("结束录制");
                    startRecording();
                }

            }
        });
    }

    private void initVideoEncoder() throws IOException {
        mVideoEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        MediaFormat format = MediaFormat.createVideoFormat(
            MediaFormat.MIMETYPE_VIDEO_AVC, VIDEO_WIDTH, VIDEO_HEIGHT);
        format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BITRATE);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAMERATE);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mVideoEncoder.start();
    }

    private void initAudioEncoder() throws IOException {
        mAudioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
        MediaFormat format = MediaFormat.createAudioFormat(
            MediaFormat.MIMETYPE_AUDIO_AAC, AUDIO_SAMPLE_RATE, 2);
        format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BITRATE);
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, 
            MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        mAudioEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mAudioEncoder.start();
    }

    @SuppressLint("MissingPermission")
    private void initAudioRecorder() {
        int bufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, 
            AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, AUDIO_SAMPLE_RATE,
            AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
        mAudioRecord.setRecordPositionUpdateListener(this);
        mAudioRecord.startRecording();
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (mIsRecording) {
            encodeVideoFrame(data);
        }
        camera.addCallbackBuffer(data);
    }

    @Override
    public void onMarkerReached(AudioRecord audioRecord) {

    }

    @Override
    public void onPeriodicNotification(AudioRecord recorder) {
//        if (mIsRecording) {
//            byte[] audioData = new byte[4096];
//            int readSize = mAudioRecord.read(audioData, 0, audioData.length);
//            if (readSize > 0) {
//                encodeAudioFrame(audioData, readSize);
//            }
//        }
    }

    private void encodeVideoFrame(byte[] yuvData) {
        try {
            int inputBufferIndex = mVideoEncoder.dequeueInputBuffer(10000);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = mVideoEncoder.getInputBuffer(inputBufferIndex);
                inputBuffer.clear();
                inputBuffer.put(yuvData);
                mVideoEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, 
                    System.nanoTime() / 1000, 0);
            }
            
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mVideoEncoder.dequeueOutputBuffer(bufferInfo, 10000);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = mVideoEncoder.getOutputBuffer(outputBufferIndex);
                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    if (!mMuxerStarted ) {
                        mMuxer.start();
                        mMuxerStarted = true;
                    }
                } else if (mMuxerStarted) {
                    bufferInfo.presentationTimeUs = System.nanoTime() / 1000 - mStartTime;
                    mMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo);
                }
                mVideoEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mVideoEncoder.dequeueOutputBuffer(bufferInfo, 0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void encodeAudioFrame(byte[] pcmData, int size) {
        try {
            int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(10000);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex);
                inputBuffer.clear();
                inputBuffer.put(pcmData, 0, size);
                mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, size, 
                    System.nanoTime() / 1000, 0);
            }
            
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 10000);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex);
                if (mMuxerStarted) {
                    bufferInfo.presentationTimeUs = System.nanoTime() / 1000 - mStartTime;
                    mMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
                }
                mAudioEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera = Camera.open();
            Camera.Parameters params = mCamera.getParameters();
            params.setPreviewSize(VIDEO_WIDTH, VIDEO_HEIGHT);
            params.setPreviewFormat(ImageFormat.NV21);
            mCamera.setParameters(params);
            mCamera.setPreviewDisplay(holder);
            mCamera.setPreviewCallbackWithBuffer(this);
            
            byte[] buffer = new byte[VIDEO_WIDTH * VIDEO_HEIGHT * 3 / 2];
            mCamera.addCallbackBuffer(buffer);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {

    }

    private void startRecording() {
        try {
            File file = new File( getExternalCacheDir()+"/videos");
            if(!file.exists()){
                file.mkdirs();
            }
            File mp4 = new File(file,System.currentTimeMillis()+".mp4");
            if(!mp4.exists()){
                try {
                    mp4.createNewFile();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            String filePathMp4 = mp4.getPath();
            mMuxer = new MediaMuxer(filePathMp4,
                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            
            initVideoEncoder();
            initAudioEncoder();
            initAudioRecorder();
            
            MediaFormat videoFormat = mVideoEncoder.getOutputFormat();
            mVideoTrackIndex = mMuxer.addTrack(videoFormat);
            
//            MediaFormat audioFormat = mAudioEncoder.getOutputFormat();
//            mAudioTrackIndex = mMuxer.addTrack(audioFormat);
            
            mStartTime = System.nanoTime() / 1000;
            mIsRecording = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private void stopRecording() {
        mIsRecording = false;
        
        if (mAudioRecord != null) {
            mAudioRecord.stop();
            mAudioRecord.release();
            mAudioRecord = null;
        }
        
        if (mVideoEncoder != null) {
            mVideoEncoder.stop();
            mVideoEncoder.release();
            mVideoEncoder = null;
        }
        
        if (mAudioEncoder != null) {
            mAudioEncoder.stop();
            mAudioEncoder.release();
            mAudioEncoder = null;
        }
        
        if (mMuxer != null) {
            if (mMuxerStarted) {
                mMuxer.stop();
            }
            mMuxer.release();
            mMuxer = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopRecording();
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }
}
